"""任务调度器模块
提供任务调度和管理功能
"""
import logging
import json
import traceback
from datetime import datetime, timedelta
from croniter import croniter

from services.shared.database import Task, TaskExecution
from services.shared.db_pool import get_db_session, close_db_session
from services.shared.models.enums import TaskStatus
from services.shared.logger import get_logger
from services.scheduler.tasks import schedule_task
from services.shared.timezone_utils import now as timezone_now, DEFAULT_TIMEZONE
from services.shared.log_utils import (log_error, log_info, log_system_event, 
                                        log_task_operation, log_system_performance)

# 获取日志记录器
logger = get_logger('scheduler.scheduler')

class TaskScheduler:
    """任务调度器
    负责管理和调度定时任务
    """
    
    def __init__(self):
        """初始化调度器"""
        self.active_tasks = {}
    
    def reload_all_tasks(self):
        """重新加载所有活跃任务
        
        Returns:
            加载结果字典
        """
        logger.debug("开始重新加载所有活跃任务")
        
        try:
            # 获取数据库会话
            session = get_db_session()
            try:
                # 查询所有活跃任务 - 使用正确的状态
                # 注意：TaskStatus 枚举中没有 ACTIVE 状态，应该使用 PENDING 或其他适当的状态
                tasks = session.query(Task).filter_by(is_enabled=True).all()
                logger.debug(f"查询所有启用的任务，不按状态过滤")
                logger.debug(f"查询到{len(tasks)}个活跃任务")
                
                # 清空当前活跃任务列表
                self.active_tasks = {}
                
                # 加载任务
                for task in tasks:
                    self._load_task(task)
                
                # 注意：任务检查现在由专门的check_tasks任务处理，避免重复触发
                logger.debug("任务加载完成，任务检查将由check_tasks任务处理")
                
                logger.info(f"成功加载{len(self.active_tasks)}个活跃任务")
                return {
                    'loaded_tasks': len(self.active_tasks),
                    'total_tasks': len(tasks)
                }
            finally:
                close_db_session()
        except Exception as e:
            error_msg = f"加载任务失败: {str(e)}"
            logger.error(error_msg)
            logger.debug(f"加载任务异常详情: {traceback.format_exc()}")
            
            # 记录系统错误日志
            log_system_event(
                level='ERROR',
                module='scheduler',
                message="重新加载所有活跃任务失败",
                details=json.dumps({
                    'error_type': type(e).__name__,
                    'error_message': str(e),
                    'traceback': traceback.format_exc(),
                    'active_tasks_count': len(self.active_tasks)
                }, ensure_ascii=False),
                action='RELOAD_TASKS',
                resource_type='SCHEDULER'
            )
            
            return {
                'error': str(e),
                'loaded_tasks': 0
            }
    
    def _load_task(self, task):
        """加载单个任务
        
        Args:
            task: 任务对象
        """
        try:
            task_id = task.id
            logger.debug(f"加载任务: {task_id}, 名称: {task.name if hasattr(task, 'name') else '未知'}")
            
            # 将任务添加到活跃任务列表
            self.active_tasks[task_id] = {
                'task': task,
                'last_check': timezone_now()
            }
            
            logger.debug(f"任务已加载: {task_id}")
        except Exception as e:
            task_id = task.id if hasattr(task, 'id') else '未知'
            error_msg = f"加载任务{task_id}失败: {str(e)}"
            logger.error(error_msg)
            
            # 记录任务操作日志
            log_task_operation(
                action='load',
                task_id=task_id,
                task_name=getattr(task, 'name', '未知'),
                user_id=None,
                execution_time=None,
                status_code=500,
                error_message=str(e),
                details=f"加载单个任务失败: {task_id}"
            )
    
    def _check_cron_tasks(self):
        """检查所有定时触发的任务
        触发符合条件的定时任务
        
        Returns:
            int: 触发的任务数量
        """
        logger.debug("开始检查定时触发的任务")
        current_time = timezone_now()
        triggered_count = 0
        
        # 直接从数据库获取所有启用的任务
        session = get_db_session()
        try:
            tasks = session.query(Task).filter_by(is_enabled=True).all()
            logger.info(f"从数据库加载了{len(tasks)}个启用的任务")
        finally:
            close_db_session()
        
        # 遍历所有启用的任务
        for task in tasks:
            task_id = task.id
            try:
                # 使用当前任务对象检查是否需要触发
                should_trigger, next_execution = self._should_trigger_cron_task(task, current_time)
                if should_trigger:
                    logger.info(f"触发定时任务: {task_id}, 名称: {task.name if hasattr(task, 'name') else '未知'}")
                
                # 如果需要触发，执行任务调度
                if should_trigger:
                    
                    # 移除并发限制检查，任务直接调度到队列
                    logger.info(f"准备调度任务{task_id}到执行队列")
                    
                    # 调用调度任务
                    try:
                        logger.info(f"准备调用schedule_task.delay({task_id})")
                        result = schedule_task.delay(task_id)
                        logger.info(f"schedule_task.delay调用成功，Celery任务ID: {result.id}")
                        triggered_count += 1  # 增加触发计数
                    except Exception as e:
                        error_msg = f"调用schedule_task.delay失败: {str(e)}"
                        logger.error(error_msg)
                        logger.error(f"异常详情: {traceback.format_exc()}")
                        
                        # 记录任务操作日志
                        log_task_operation(
                            action='schedule',
                            task_id=task_id,
                            task_name=getattr(task, 'name', '未知'),
                            user_id=None,
                            execution_time=None,
                            status_code=500,
                            error_message=str(e),
                            details=f"调度任务失败: {task_id}"
                        )
                        continue
                    
                    # 立即计算并记录下次执行时间
                    try:
                        # 将aware datetime转换为naive datetime用于croniter
                        current_time_naive = current_time.replace(tzinfo=None)
                        cron = croniter(task.cron_expression, current_time_naive)
                        next_run = cron.get_next(datetime)
                        # 将naive datetime转换为aware datetime
                        next_run_aware = DEFAULT_TIMEZONE.localize(next_run)
                        logger.info(f"任务{task_id}的下次执行时间更新为: {next_run_aware}")
                    except Exception as e:
                        error_msg = f"计算任务{task_id}的下次执行时间失败: {str(e)}"
                        logger.error(error_msg)
                        
                        # 记录系统事件日志
                        log_system_event(
                            action='CALCULATE_NEXT_RUN',
                            resource_type='TASK',
                            resource_id=str(task_id),
                            details=f"计算下次执行时间失败: {task_id}",
                            metadata={
                                'task_id': task_id,
                                'task_name': getattr(task, 'name', '未知'),
                                'cron_expression': getattr(task, 'cron_expression', '未知'),
                                'error_type': type(e).__name__,
                                'error_message': str(e),
                                'traceback': traceback.format_exc(),
                                'current_time': str(current_time)
                            },
                            level='ERROR'
                        )
                        pass
            except Exception as e:
                error_msg = f"检查定时任务{task_id}失败: {str(e)}"
                logger.error(error_msg)
                
                # 记录系统事件日志
                log_system_event(
                    level='ERROR',
                    module='scheduler',
                    message=f"检查定时任务失败: {task_id}",
                    details=json.dumps({
                        'task_id': task_id,
                        'task_name': getattr(task, 'name', '未知'),
                        'error_type': type(e).__name__,
                        'error_message': str(e),
                        'traceback': traceback.format_exc(),
                        'current_time': str(current_time)
                    }, ensure_ascii=False),
                    action='CHECK_CRON_TASK',
                    resource_type='TASK',
                    resource_id=str(task_id)
                )
        
        logger.debug(f"定时任务检查完成，触发了{triggered_count}个任务")
        return triggered_count
    
    def _should_trigger_cron_task(self, task, current_time):
        """检查定时任务是否应该触发
        
        正确的cron调度逻辑：基于上次执行时间计算下次执行时间，而不是简单匹配当前时间
        参考Celery Beat和其他主流调度系统的实现方式
        
        Args:
            task: 任务对象
            current_time: 当前时间
            
        Returns:
            tuple: (是否应该触发, 下次执行时间)
        """
        try:
            # 获取Cron表达式
            cron_expression = task.cron_expression
            if not cron_expression:
                logger.warning(f"任务{task.id}的Cron表达式为空")
                return False, None
            
            logger.info(f"检查任务{task.id}是否应该触发，Cron表达式: {cron_expression}, 当前时间: {current_time}")
            
            # 转换为无时区的时间进行计算
            current_time_naive = current_time.replace(tzinfo=None)
            
            # 获取最近一次执行时间
            session = get_db_session()
            last_execution_time = None
            try:
                execution = session.query(TaskExecution).\
                    filter_by(task_id=task.id).\
                    order_by(TaskExecution.started_at.desc()).\
                    first()
                
                if execution:
                    last_execution_time = execution.started_at
                    if last_execution_time.tzinfo:
                        last_execution_time = last_execution_time.replace(tzinfo=None)
                    logger.debug(f"任务{task.id}上次执行时间: {last_execution_time}")
                else:
                    logger.debug(f"任务{task.id}没有执行历史")
            finally:
                close_db_session()
            
            # 选择合适的基准时间来计算下次执行时间
            if last_execution_time is None:
                # 对于新任务，直接基于当前时间计算下次执行时间
                base_time = current_time_naive
                logger.debug(f"任务{task.id}无执行历史，基于当前时间计算")
            else:
                # 对于有执行历史的任务，检查上次执行时间是否太久远
                time_diff = current_time_naive - last_execution_time
                # 如果上次执行时间超过2分钟，说明可能错过了多次执行，应该基于当前时间重新计算
                if time_diff.total_seconds() > 120:  # 2分钟
                    base_time = current_time_naive
                    logger.debug(f"任务{task.id}上次执行时间过久({last_execution_time})，基于当前时间重新计算")
                else:
                    base_time = last_execution_time
                    logger.debug(f"任务{task.id}使用上次执行时间作为基准: {base_time}")
            
            cron = croniter(cron_expression, base_time)
            next_execution_time = cron.get_next(datetime)
            logger.debug(f"任务{task.id}基于{base_time}计算下次执行时间: {next_execution_time}")
            
            # 计算再下一次执行时间，用于返回
            next_next_execution = cron.get_next(datetime)
            # 将naive datetime转换为aware datetime
            next_next_execution_aware = DEFAULT_TIMEZONE.localize(next_next_execution)
            
            # 将时间向下取整到分钟进行比较（cron的最小单位是分钟）
            current_minute = current_time_naive.replace(second=0, microsecond=0)
            next_execution_minute = next_execution_time.replace(second=0, microsecond=0)
            
            # 判断是否应该触发：使用croniter进行精确匹配
            # 从当前分钟的前一分钟开始计算，看下一次执行时间是否是当前分钟
            prev_minute = current_minute - timedelta(minutes=1)
            cron_checker = croniter(cron_expression, prev_minute)
            next_scheduled = cron_checker.get_next(datetime)
            next_scheduled_minute = next_scheduled.replace(second=0, microsecond=0)
            
            # 精确匹配：如果从前一分钟计算的下次执行时间是当前分钟，说明应该触发
            should_trigger = (next_scheduled_minute == current_minute)
            
            logger.debug(f"任务{task.id} cron检查详情:")
            logger.debug(f"  - 当前分钟: {current_minute}")
            logger.debug(f"  - cron表达式: {cron_expression}")
            logger.debug(f"  - 匹配结果: {should_trigger}")
            if last_execution_time is None:
                logger.debug(f"  - 基准时间: {current_time_naive} (当前时间，无执行历史)")
            else:
                logger.debug(f"  - 基准时间: {base_time} (上次执行时间)")
            logger.debug(f"  - 下次执行时间: {next_execution_time}")
            logger.debug(f"  - 下次执行分钟: {next_execution_minute}")
            logger.debug(f"  - 应该触发: {should_trigger} (时间比较: {current_minute} == {next_execution_minute})")
            
            # 如果应该触发，还需要检查是否在同一分钟内已经执行过（防止重复触发）
            if should_trigger:
                if last_execution_time:
                    last_execution_minute = last_execution_time.replace(second=0, microsecond=0)
                    if last_execution_minute == next_execution_minute:
                        logger.info(f"任务{task.id}在预定执行时间{next_execution_minute}已经执行过，跳过触发")
                        should_trigger = False
                    else:
                        logger.info(f"任务{task.id}符合触发条件，预定执行时间: {next_execution_minute}")
                else:
                    logger.info(f"任务{task.id}首次执行，符合触发条件，预定执行时间: {next_execution_minute}")
            else:
                logger.debug(f"任务{task.id}不符合触发条件，下次执行时间: {next_execution_time}")
            
            return should_trigger, next_next_execution_aware
        except Exception as e:
            task_id = getattr(task, 'id', '未知')
            error_msg = f"检查任务{task_id}是否应该触发失败: {str(e)}"
            logger.error(error_msg)
            logger.debug(f"异常详情: {traceback.format_exc()}")
            
            # 记录系统事件日志
            log_system_event(
                level='ERROR',
                module='scheduler',
                message=f"检查任务触发条件失败: {task_id}",
                details=json.dumps({
                    'task_id': task_id,
                    'task_name': getattr(task, 'name', '未知'),
                    'cron_expression': getattr(task, 'cron_expression', '未知'),
                    'error_type': type(e).__name__,
                    'error_message': str(e),
                    'traceback': traceback.format_exc(),
                    'current_time': str(current_time)
                }, ensure_ascii=False),
                action='CHECK_TRIGGER_CONDITION',
                resource_type='TASK',
                resource_id=str(task_id)
            )
            return False, None